import { TrayOperator } from "./tray.operator";
import { WindowOperator } from "./window.operator";
import { RunTime } from "../runtime";
import { LibUniqueID } from "@lib/alen-helper/node/uniqueID";
import { EventClass } from "@lib/alen-helper/node/baseClass";
import { filter, of, Subscription, Observable, Subject, timer, map } from "rxjs";
import { get_back_observable } from "@lib/alen-helper/node/fun";
import { Application } from "../application/application";

const _Operators: OperatorManager.OperatorCls = {
    'tray': TrayOperator,
    'window': WindowOperator,
}

export class OperatorManager extends EventClass<{
    build: { id: string, appid: string, type: OperatorManager.OperatorType },
    destroy: { id: string, appid: string, type: OperatorManager.OperatorType },
    state: { id: string, appid: string, type: OperatorManager.OperatorType, state: OperatorManager.State },
}> {
    constructor(
        public readonly runtime: RunTime,
        options: Partial<OperatorManager.Options>,
        BaseOptions?: Partial<EventClass.BaseOptions>
    ) {
        super('OperatorManager', BaseOptions);
        this._options = Object.assign(this._options, options);
    }

    private _options: OperatorManager.Options = {
        timeout: 5000,
    }

    private _items: { item: OperatorManager.Item, subs: Subscription }[] = [];

    private _buildID() {
        return LibUniqueID.id16();
    }

    private _buildOperator<T extends OperatorManager.OperatorType>(type: T, options: Partial<OperatorManager.OperatorOptions<T>>, app: Application, id: string) {
        let cls = _Operators[type];
        const operator: OperatorManager.Operator<T> = this._newClass(cls as any, id, app, options as any) as any;
        return operator;
    }

    build<T extends OperatorManager.OperatorType = OperatorManager.OperatorType>(appid: string, type: T, options: Partial<OperatorManager.OperatorOptions<T>>) {
        const id = this._buildID();
        const app = this.runtime.getApp(appid);
        if (!app) throw this._err('项目不可用', '找不到APP:' + appid);
        const operator: OperatorManager.Operator<'window'> = this._buildOperator(type, options, app, id);
        const state: OperatorManager.State = OperatorManager.State.none;
        const state$ = new Subject<OperatorManager.State>();

        const subs = this.event('destroy')
            .pipe(filter(e => (e.appid === appid) && (e.id === id)))
            .subscribe(() => {
                this._remove(id);
            });

        operator.addEventHandler('destroy', e => {
            this._setState(item, OperatorManager.State.destroyed);
            return this.triggerEventEnd('destroy', { id, appid, type: operator.type })
                .pipe(map(() => null));
        });

        operator.addEventHandler('start', e => {
            this._setState(item, OperatorManager.State.started);
        });

        const item: OperatorManager.Item<T> = { appid, id, state, state$, operator: operator as OperatorManager.Operator<T>, data: null };
        this._items.push({ item, subs });
        this.triggerEvent('build', { id, appid, type: operator.type });
        this._setState(item, OperatorManager.State.none);
        return item;
    }

    private _remove(id: string) {
        const item = this._get(id);
        if (!item) return false;

        item.subs.unsubscribe();
        item.item.state$.complete();
        item.item.operator.destroyEvent();
        this._items.splice(item.index, 1);

        return true;
    }

    private _get<T extends OperatorManager.OperatorType = OperatorManager.OperatorType>(id: string): null | { index: number, item: OperatorManager.Item<T>, subs: Subscription } {
        for (let i = 0; i < this._items.length; i++) {
            if (this._items[i].item.id === id) {
                return {
                    index: i,
                    item: this._items[i].item,
                    subs: this._items[i].subs,
                };
            }
        }
        return null;
    }

    private _setState(item: OperatorManager.Item, state: OperatorManager.State) {
        item.state = state;
        item.state$.next(item.state);
        this._log.debug(`state [ ${item.operator.type} ] state:${item.state}`);
        this.triggerEvent('state', { id: item.id, appid: item.appid, type: item.operator.type, state: item.state });
    }

    getItem<T extends OperatorManager.OperatorType = OperatorManager.OperatorType>(appid: string | null, id: string) {
        const data = this._get<T>(id);
        if (!data) return null;
        if (!appid) return data.item;
        return (data.item.appid === appid) ? data.item : null;
    }

    find<T extends OperatorManager.OperatorType = OperatorManager.OperatorType>(appid: string | null, ...types: T[]) {
        const arr: OperatorManager.Item<T>[] = [];
        for (let item of this._items) {
            if (appid && item.item.appid !== appid) break;
            if (types.length === 0 || (types.indexOf(item.item.operator.type as T) !== -1)) arr.push(item.item);
        }
        return arr;
    }

    /**
     * 销毁 Operator
     * @param appid 
     * @param id 
     * @returns 找不到时返回false；状态为 destroyed 时返回true；状态为 none、start、started 时正常操作，状态为 destroy 时等待状态改变为 started 时返回true；
     */
    destroy(appid: string, id: string) {
        return new Observable<boolean>(obs => {
            const item = this.getItem(appid, id);
            if (!item) {
                obs.next(false);
                obs.complete();
                return;
            }

            if (item.state === OperatorManager.State.destroyed) {
                //已销毁
                obs.next(true);
                obs.complete();
                return;
            }

            const endFun = () => {
                subs.unsubscribe();
            }

            const subs = item.state$.pipe(filter(s => s === OperatorManager.State.destroyed))
                .subscribe(() => {
                    endFun();
                    obs.next(true);
                    obs.complete();
                });

            if (this._options.timeout > 0) {
                subs.add(
                    timer(this._options.timeout)
                        .subscribe(() => {
                            endFun();
                            obs.error(this._err('超时', `[ ${item.operator.type} ] id:${item.id}`));
                        })
                );
            }

            if (item.state !== OperatorManager.State.destroy) {
                this._setState(item, OperatorManager.State.destroy);
                this._log.debug(`destroy... [ ${item.operator.type} ] id:${item.id}`);
                subs.add(
                    get_back_observable(item.operator.destroy())
                        .subscribe({
                            complete: () => {
                                //
                            },
                            error: err => {
                                endFun();
                                obs.error(err);
                            }
                        })
                );
            }
            return endFun;
        });
    }

    /**
     * 开始 Operator
     * @param appid 
     * @param id 
     * @returns 找不到时返回false；状态为 started、destroy、destroyed 时返回true；状态为 start 时等待状态改变为 started 时返回true；状态为 none 时正常操作；过程中如果 destroy 则抛错
     */
    start(appid: string, id: string) {
        return new Observable<boolean>(obs => {
            const item = this.getItem(appid, id);
            if (!item) {
                obs.next(false);
                obs.complete();
                return;
            }
            if ([OperatorManager.State.started, OperatorManager.State.destroy, OperatorManager.State.destroyed].indexOf(item.state) !== -1) {
                obs.next(true);
                obs.complete();
                return;
            }


            const endFun = () => {
                subs.unsubscribe();
            }

            const subs = item.state$.pipe(filter(s => s === OperatorManager.State.started))
                .subscribe(() => {
                    endFun();
                    obs.next(true);
                    obs.complete();
                });

            if (this._options.timeout > 0) {
                subs.add(
                    timer(this._options.timeout)
                        .subscribe(() => {
                            endFun();
                            obs.error(this._err('超时'));
                        })
                );
            }
            subs.add(
                item.state$.pipe(filter(s => s === OperatorManager.State.destroy))
                    .subscribe(() => {
                        endFun();
                        obs.error(this._err('已取消', 'operator 销毁'));
                    })
            );
            if (item.state === OperatorManager.State.none) {
                this._setState(item, OperatorManager.State.start);
                subs.add(
                    get_back_observable(item.operator.start())
                        .subscribe({
                            complete: () => {

                            },
                            error: err => {
                                endFun();
                                obs.error(err);
                            }
                        })
                );
            }
            return endFun;
        });
    }
}

export namespace OperatorManager {
    export interface Options {
        timeout: number,
    }


    export type Operators = Runtime.OperatorCls.Tray | Runtime.OperatorCls.Window;

    export type OperatorCls<O extends Operators = Operators> = Merge<O extends Runtime.Operator<infer N, infer OP, any> ? { [n in N]: (new (id: string, app: Application, op: Partial<OP>) => O) } : never>
    export type OperatorType = keyof OperatorCls;
    export type Operator<T extends OperatorType> = OperatorCls[T] extends (new (...ps: any[]) => infer O) ? (O extends Runtime.Operator<any, any, any> ? O : never) : never;
    export type OperatorOptions<T extends OperatorType> = Operator<T>['options'];

    export enum State {
        /**已创建，未开始 */
        none,
        /**开始 */
        start,
        /**已开始 */
        started,
        /**销毁 */
        destroy,
        /**已销毁 */
        destroyed,
    };

    export interface Item<T extends OperatorType = OperatorType, D = any> {
        id: string,
        appid: string,
        state: State,
        state$: Subject<State>,
        operator: Operator<T>,
        data: D | null,
    }

}